# = Class Matrix
# Class that represents a mathematical matrixe
class Matrix
  # Specific type of the matrix contained in the instance  
  attr_reader :type
  attr_writer :type
  # Array containing the declared matrix
  attr_reader :value
  
  # Constructor of the class Matrix
  # === Parameters
  # * value: Row Vector array containing the matrix that wants to be created
  def initialize value
    @value = []
    @type = 'mat'
    for v in value
      @value.push(v.value)
    end
  end

  # Method that performs the operation self + obj, includes coercion when obj
  # is a valid Vector.
  # === Parameters:
  # * obj -> Element of type Matrix or Vector that is to be added to self
  # === Return value:
  # Matrix object containing the result of the operation
  def add obj
    mat = obj.matrix
    if mat.size != size
      return "Los tamaños de las matrices no concuerdan " +
             "'#{mat.size[0]}x#{mat.size[1]}', '#{size[0]}x#{size[1]}'"
    end

    nval = @value
    lvec = []
    for i in (0..mat.size[0]-1)
      for j in (0..mat.size[1]-1)
        nval[i][j] += mat.value[i][j]
      end
      lvec[i] = Vector.new nval[i]
      lvec[i].type = 'vec_fil'
    end

    Matrix.new lvec
  end

  # Method that performs the operation self - obj, includes coercion when obj
  # is a valid Vector.
  # === Parameters:
  # * obj -> Element of type Matrix or Vector that is to be substracted from self
  # === Return value:
  # Matrix object containing the result of the operation
  def minus obj
    mat = obj.matrix
    if mat.size != size
      return "Los tamaños de las matrices no concuerdan " +
             "'#{mat.size[0]}x#{mat.size[1]}', '#{size[0]}x#{size[1]}'"
    end

    nval = @value
    lvec = []
    for i in (0..mat.size[0]-1)
      for j in (0..mat.size[1]-1)
        nval[i][j] -= mat.value[i][j]
      end
      lvec[i] = Vector.new nval[i]
      lvec[i].type = 'vec_fil'
    end

    Matrix.new lvec
  end

  # Method that performs the operation - self
  # === Return value:
  # Matrix object containing the result of the operation
  def uminus

    nval = @value
    lvec = []
    for i in (0..size[0]-1)
      for j in (0..size[1]-1)
        nval[i][j] = - nval[i][j]
      end
      lvec[i] = Vector.new nval[i]
      lvec[i].type = 'vec_fil'
    end

    Matrix.new lvec
  end

  # Method that performs the operation self / int
  # === Parameters:
  # * int -> Element of type 'num' that is to be divided to self
  # === Return value:
  # Matrix object containing the result of the operation
  def div int

    nval = @value
    lvec = []
    for i in (0..size[0]-1)
      for j in (0..size[1]-1)
        nval[i][j] /= int
      end
      lvec[i] = Vector.new nval[i]
      lvec[i].type = 'vec_fil'
    end

    Matrix.new lvec
  end

  # Method that performs the operation self */* int
  # === Parameters:
  # * int -> Element of type 'num' that is to be multiplied to self
  # === Return value:
  # Matrix object containing the result of the operation
  def prod int

    nval = @value
    lvec = []
    for i in (0..size[0]-1)
      for j in (0..size[1]-1)
        nval[i][j] *= int
      end
      lvec[i] = Vector.new nval[i]
      lvec[i].type = 'vec_fil'
    end

    Matrix.new lvec
  end

  
  # Method that performs the matrix multiplication self * obj, makes coercion when obj is
  # a valid Vector
  # === Parameters:
  # * obj -> Element that is to be multiplied to self
  # === Return value:
  # Matrix object containing the result of the operation
  def mat_prod obj
    mat = obj.matrix
    if size[1] != mat.size[0]
      return "El ancho de la primera matriz no concuerda con el alto de la segunda " +
             "'#{size[0]}x#{size[1]}', '#{mat.size[0]}x#{mat.size[1]}'"
    end
    
    # Find rows of Matrix A
    lvecA = []
    for row in @value
      lvecA.push Vector.new(row)
    end

    # Find cols of Matrix B
    matT = mat.trasp
    lvecB = []
    for row in matT.value
      lvecB.push Vector.new(row)
    end
    
    # Get scalar product between each possible pair
    lvec = []
    for m in (0..lvecA.length - 1)
      vec = []
      for p in (0..lvecB.length - 1)
        v = lvecA[m].scalar_prod(lvecB[p])
        if v.kind_of?String
          return v
        end
        vec.push v
      end
      v = Vector.new vec
      v.type = 'vec_fil'
      lvec.push v
    end
 
    Matrix.new lvec
  end
  
  # Method that performs the operation to traspose self
  # === Return value:
  # Matrix object containing the result of the operation
  def trasp
    nval = []
    for i in (0..size[1]-1)
      nval[i] = []
      for j in (0..size[0]-1)
        nval[i][j] = @value[j][i]
      end
    end
    lvec = []
    for row in nval
      v = Vector.new row
      v.type = 'vec_fil'
      lvec.push v
    end

    Matrix.new lvec
  end

  def assign range, val
    if range[0].kind_of?Fixnum
      value[range[0]][range[1]] = val
    else
      for i in (range[0][0]..range[0][1])
        for j in (range[1][0]..range[1][1])
          value[i][j] = val.value[i - range[0][0]][j - range[1][0]]
        end
      end
    end
    nil
  end

  # Method that transforms self into a valid Matrix object
  # === Return value
  # Matrix object containing the equivalent matrix to the one contained in self
  def matrix
    self
  end
  
  # Method that transforms the array into a printable string
  # === Return value
  # * String output -> String representation of the matrix
  def to_s
    max_pad = 0;
    for r in @value
      for v in r
        max_pad = v.to_s.length > max_pad ? v.to_s.length : max_pad
      end
    end
    ++ max_pad
    output = "\n"
    for r in @value
      output += '|'
      for v in r
        output += sprintf("%#{max_pad}.1f ", v)
      end
      output += "|\n"
    end
    output
  end

  # Method that returns the dimensions of self
  # === Return value
  # Array containing the dimensions of self as follows:
  # * size[0] = number of rows
  # * size[1] = number of columns
  def size
    return [@value.length, @value[0].length]
  end
end